跳到主要内容

NodeJS 命令行程序和文件操作

命令行程序

读取控制台输入

文档示例

const readline = require('readline');

// 实现这个接口,传入标准输入输出流
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// 把输入当作参数传入回调函数
rl.question('你如何看待 Node.js 中文网?\n', (answer) => {
// TODO:将答案记录在数据库中。
console.log(`感谢您的宝贵意见: ${answer}`);

rl.close();
});

'line' 事件

每当 input 流接收到行尾输入(\n、 \r 或 \r\n)时就会触发 'line' 事件。 这种情况通常发生在当用户按下 <Enter> 键或 <Return> 键。

调用监听器函数时会带上包含接收到的那一行输入的字符串。

rl.on('line', (input) => {
console.log(`接收到: ${input}`);
});

关闭输入输出流

rl.close() 方法会关闭 readline.Interface 实例,并放弃对 input 和 output 流的控制;当调用时,将触发 'close' 事件。

但是调用 rl.close() 不会立即停止 readline.Interface 实例触发的 其他事件(例如 'line')。

采集系统实例

const readline = require('readline');
const fs = require('fs');

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

let persion = {
name: null,
gender: null,
birthday: null,
job: null
};

let index = 0;
const list = [
{
question: '请确认是否立即输入信息?(Y/N):',
handler: (answer) => {
answer = answer.trim();
if (answer === '' || answer.toLowerCase() === 'y') {
console.log('开始输入录入操作');
index++;
console.log(list[index].question);

} else if (answer.toLowerCase() === 'n') {
console.log('退出本次输入录入操作');
process.exit(0);

} else {
console.log('输入无效请重新输入');
}
}
},
{
question: '请输入用户名:[1-10字母]',
handler: (answer) => {
answer = answer.trim();
// 使用一个正则表达式字面量,其由包含在斜杠之间的模式组成
// 例如 var re = /ab+c/; 等价 var re = new RegExp("ab+c");
// 更多参考: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
if (/^[a-zA-Z]{1,10}$/.test(answer)) {
persion.name = answer;
index++;
console.log(list[index].question);

} else {
console.log('\n输入无效\n请输入长度1-10的字母\n');
}
}
},
{
question: '请输入性别 [数字] \n1. 男 \n2. 女',
handler: (answer) => {
answer = answer.trim();
if (/^[12]{1}$/.test(answer)) {
persion.gender = (answer == 1 ? '男' : '女');
index++;
console.log(list[index].question);

} else {
console.log('输入错误');
}
}
},
{
question: '请输入您的生日(如:2000-01-01)',
handler: (answer) => {
answer = answer.trim();
// 输入2000-5-5也正确
if ( /^(19|20)\d{2}-(1[0-2]|0?[1-9])-(0?[1-9]|[1-2][0-9]|3[0-1])$/.test(answer)) {
persion.birthday = answer;
index++;
console.log(list[index].question);

} else {
console.log('输入错误');
}
}
},
{
question: `请选择职业 [数字] \n0. 程序员 \n1. 学生 \n2. 老师 \n3. 美术 \n4. 其他`,
handler: (answer) => {
answer = answer.trim();
if (/^[0-5]{1}$/.test(answer)) {
let temp = ['程序员', '学生', '老师', '美术', '其他']
persion.job = temp[answer]
index++;
console.log(list[index].question);

} else {
console.log('输入错误');
}
}
},
{
question: `请确认是否保存信息?(Y/N):`,
handler: (answer) => {
answer = answer.trim();
if (answer === '' || answer.toLowerCase() === 'y') {
/* stringify:
第二个参数是处理序列化的(传入一个回调),null表示自动处理
第三个参数是 如果参数是个数字,它代表有多少的空格(如果传入字符串就取字符串长度,最大10)
*/
fs.writeFileSync('./' + persion.name + '.json', JSON.stringify(persion, null, 4));
console.log('保存完毕退出程序');
process.exit(0);

} else if (answer.toLowerCase() === 'n') {
console.log('不保存本次录入');
process.exit(0);

} else {
console.log('输入无效请重新输入');
}
}
},
];

rl.on('line', (answer) => {
list[index].handler(answer);
});

// 第一个问题要先抛出来
console.log(list[0].question);

标准命令行程序模块

项目地址 中文文档

一般开发有各种参数的命令行程序使用的是这个 commander 模块,可以给命令行程序写一个(参数)完整的帮助文档

# 安装依赖
npm install commander

设置启动方式

#! /usr/bin/env node

表示默认使用 NodeJS 来执行,不过在写 git 钩子时已经说了,Windows 平台不适应这个,Windows 是通过文件后缀来判断执行文件的

#! /usr/bin/env node

const commander = require('commander'); // include commander in git clone of commander repo
const program = new commander.Command();

program
.option('-d, --debug', 'output extra debugging')
.option('-s, --small', 'small pizza size')
.option('-p, --pizza-type <type>', 'flavour of pizza');

program.parse(process.argv);

if (program.debug) console.log(program.opts());
console.log('pizza details:');
if (program.small) console.log('- small pizza size');
if (program.pizzaType) console.log(`- ${program.pizzaType}`);

输入 -h

node "d:\javaScript_Project\studyExpress\01.js" -h
Usage: 01 [options]

Options:
-d, --debug output extra debugging
-s, --small small pizza size
-p, --pizza-type <type> flavour of pizza
-h, --help display help for command

输入其参数

node "d:\javaScript_Project\studyExpress\01.js" -d -s -p String
{ debug: true, small: true, pizzaType: 'String' }
pizza details:
- small pizza size
- String

进程相关

参考资料 中文文档--process

process 顾名思义 与进程相关的全局变量

// 打印一下这个对象就能知道包含了哪些东西了
console.log(process)

作用:它是用于描述当前 NodeJS 进程状态的对象,提供了一个与操作系统的简单接口。通常在写本地命令行程序的时候会用到

进程属性(读/修改) 进程方法(调用执行) 进程事件(监听处理)

beforeExit 事件

NodeJS退出之前会执行这个事件,回调函数的参数是退出状态码(就像 java的 -1表示非正常退出)

beforeExit事件与exit事件的主要区别是,beforeExit的监听函数可以部署异步任务,而exit不行

使用例:

process.on('beforeExit', (code) => {
console.log('进程 beforeExit 的退出状态码: ', code);
});

exit 事件

监听器函数必须只执行同步操作

process.on('exit', (code) => {
console.log(`退出码: ${code}`);
});

uncaughtException 事件

当有未捕获的异常抛出时,可以用这个做最后的补救

参数: err <Error> 未捕获的异常 origin <string> 这个参数可以不添加 标明异常的 来源

process.on('uncaughtException', (err, origin) => {
fs.writeSync(
process.stderr.fd,
`捕获的异常: ${err}\n` +
`异常的来源: ${origin}`
);
// 再执行退出函数
// 0 表示正常退出
// 1 使用失败代码退出
process.exit(1)
});

setTimeout(() => {
console.log('这里仍然会运行');
}, 500);

// 故意引起异常,但不要捕获它。
nonexistentFunc();
console.log('这里不会运行');

文件全路径名和目录名

__filename__dirname 用于返回当前模块的文件全路径名和目录名

console.log(__filename)
// 输出为: d:\javaScript_Project\studyExpress\01.js

console.log(__dirname)
// 输出为:d:\javaScript_Project\studyExpress

路径模块

const path = require("path")

// 格式化路径
console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));

// 连接路径
console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));

// 转换为绝对路径
console.log('resolve : ' + path.resolve('main.js'));

// 路径中文件的后缀名
console.log('ext name : ' + path.extname('main.js'));

// 输出
// normalization : /test/test1/2slashes/1slash
// joint path : /test/test1/2slashes/1slash
// resolve : /web/com/1427176256_27423/main.js
// ext name : .js

一般 path 模块配合上面的 __filename__dirname 使用

console.log(path.join(__dirname, '/assets/temp.txt'));

文件操作

参考资料 中文文档 参考资料 中文文档

readFileSync 同步读取

这个 readFileSync 的原理就是全部读取到 buffer 里,所以需要使用 toString

const fs = require('fs');
// 同步读取文件的全部内容。 默认字符编码是UTF-8
const content = fs.readFileSync('./temp.txt');
console.log(content.toString());

因为这是同步读取,所以可以直接通过 try-Catch 来捕获异常

const fs = require('fs');
try {
const content = fs.readFileSync('./tep.txt');
console.log(content.toString());
} catch (error) {
console.log('错误原因:' + error.message + '\n 错误的位置: \n' + error.stack);
}

readFile 异步读取

注意 readFile 与上面的 readFileSync 区别就是 readFileSync 是同步的,readFile 是异步的;所以这个 readFile 通过回调函数处理异常

const fs = require('fs');

fs.readFile('./tem.txt', (err, content) => {
if(err){
console.log('错误原因:\n' + err.message + '\n 错误的位置: \n' + err.stack);
return;
}

console.log(content.toString());
})

因为这个 readFile 返回值是 Promise 对象,所以也可以使用 Promise 的形式进行异步编程

const fsp = require('fs').promises

fsp.readFile('./tep.txt')
.then(content => {
console.log(content.toString());
})
.catch(err => {
console.log('错误原因:\n' + err.message + '\n 错误的位置: \n' + err.stack);
})

亦或者使用 ES6 的新关键字 async/await 进行异步编程,这样还能使用 try-Catch 来捕获异常

const fsp = require('fs').promises;

const readFile = async ()=>{
try {
let constent = await fsp.readFile('./temp.txt');
console.log(constent.toString());
} catch (error) {
console.log('错误原因:\n' + err.message + '\n 错误的位置: \n' + err.stack);
}
};

readFile();

writeFile 异步写入

异步写入到文件里,不等待回调就对同一个文件多次使用 fs.writeFile() 是不安全的

fs.writeFile('文件.txt', 'Node.js 中文网', 'utf8', callback);

这个可以像上面那样进行异步操作

writeFileSync 同步写入

同步写入到文件里面

fs.writeFileSync(fileName, str, 'utf8');

可以使用 appendFile 对文件末尾追加内容